#include "stdafx.h"

namespace classifier_test
{

namespace vem
{

vem_concept_cutter::vem_concept_cutter()
{
	BaseBlockSize = 20;
	MoveOffset = 1;
	Amplification = 2;
	Prune = true;
}

vem_concept_cutter::vem_concept_cutter(const vem_concept_cutter& Val)
	: concept_cutter(Val)
{
	BaseBlockSize = Val.BaseBlockSize;
	MoveOffset = Val.MoveOffset;
	Amplification = Val.Amplification;
	Prune = Val.Prune;
}

smart_ptr<concept_cutter> vem_concept_cutter::clone() const
{
	return make_smart_ptr(new vem_concept_cutter(*this));
}

void vem_concept_cutter::serialize(serializer& Serializer)
{
	concept_cutter::serialize(Serializer);
	Serializer
		("BaseBlockSize", BaseBlockSize)
		("MoveOffset", MoveOffset)
		("Amplification", Amplification)
		("Prune", Prune);
}

void vem_concept_cutter::initialize_options(command_line_options& Options) const
{
	concept_cutter::initialize_options(Options);
	Options.add("block").set_argument("<count>").set_info("# of records in a base block, default 20");
	Options.add("offset").set_argument("(0.0,1.0]").set_info("Move offset of blocks, default 1.0");
	Options.add("amplification").set_argument("(1.0,2.0]").set_info("Amplification rate of blocks, default 2.0");
	Options.add("prune").set_argument("{0,1}").set_info("Whether improve performance by heuristically pruning large classifiers (may affect the result), default 1");
}

void vem_concept_cutter::apply_arguments(const command_line_arguments& Args)
{
	concept_cutter::apply_arguments(Args);
	BaseBlockSize = to_int(Args["block"].get_or_default("20"));
	MoveOffset = to_double(Args["offset"].get_or_default("1"));
	Amplification = to_double(Args["amplification"].get_or_default("2"));
	Prune = to_int(Args["prune"].get_or_default("1")) != 0;
}

void vem_concept_cutter::set_base_block_size(int Val)
{
	BaseBlockSize = Val;
}

void vem_concept_cutter::set_move_offset(double Val)
{
	MoveOffset = Val;
}

void vem_concept_cutter::set_amplification(double Val)
{
	Amplification = Val;
}

void vem_concept_cutter::set_prune(bool Val)
{
	Prune = Val;
}

void vem_concept_cutter::compute()
{
	logger::task LTask = clogger()->fine("Cutting data stream into concept occurrences");
	init_levels();
	init_blocks();
	find_best_cuts();
	find_chunks();
	EstVError = (double)BestErrors[Data.size()] / Data.size();
	reset_vector(Levels);
	reset_vector(Blocks);
	reset_vector(BestErrors);
	reset_vector(BestCuts);
	clogger()->fine("# of chunks: " + to_string(Chunks.size()));
}

int vem_concept_cutter::get_chunk_count() const
{
	return Chunks.size();
}

const concept_cutter::chunk& vem_concept_cutter::get_chunk(int I) const
{
	return Chunks[I];
}

double vem_concept_cutter::compute_estimated_validation_error() const
{
	return EstVError;
}

void vem_concept_cutter::init_levels()
{
	int Size = BaseBlockSize;
	while (Size <= Data.size())
	{
		Levels.push_back(level());
		level& Level = Levels.back();
		Level.size = Size;
		Level.offset = int(Size * MoveOffset);
		if (Level.offset == 0) Level.offset = 1;
		Size = int(Size * Amplification);
		if (Size == Level.size) Size++;
	}
}

void vem_concept_cutter::init_blocks()
{
	Blocks.resize(Levels.size());
	for (int Lev = 0; Lev < (int)Levels.size(); Lev++)
		Blocks[Lev].resize((Data.size() - Levels[Lev].size + Levels[Lev].offset) / Levels[Lev].offset);
}

void vem_concept_cutter::find_best_cuts()
{
	logger::task LTask = clogger()->fine("Finding best cuts");
	BestErrors.assign(Data.size() + 1, numeric_limits<double>::max());
	BestCuts.assign(Data.size() + 1, -1);
	BestErrors[0] = 0;
	vector<double> LevErrors(Levels.size(), numeric_limits<double>::max());
	vector<int> LevCuts(Levels.size(), -1);
	for (int Pos = 1; Pos <= Data.size(); Pos++)
	{
		for (int Lev = 0; Lev < (int)Levels.size(); Lev++)
		{
			if (Pos < Levels[Lev].size) continue;
			int BlockID = (Pos - Levels[Lev].size) / Levels[Lev].offset;
			if (Pos != BlockID * Levels[Lev].offset + Levels[Lev].size)
			{
				if (LevCuts[Lev] >= 0 &&
					Blocks[Lev][BlockID].ensemble_classifier->classify(*Data[Pos - 1]) != Data[Pos - 1]->back().discr)
					LevErrors[Lev]++;
				if (BlockID > 0)
					Blocks[Lev][BlockID - 1].vclassifier.reset(),
					Blocks[Lev][BlockID - 1].ensemble_classifier.reset();
			}
			else
			{
				if (Levels[Lev].size >= 100)
					LTask.progress(to_string(Pos) + "/" + to_string(Data.size()));
				block& Block = Blocks[Lev][BlockID];
				LevErrors[Lev] = numeric_limits<double>::max();
				LevCuts[Lev] = -1;
				Block.verror = -1;
				if (!good_block(Pos, Lev)) continue;
				make_block(Block, Pos - Levels[Lev].size, Pos);
				int LeftBound = left_bound_of_block(Pos, Lev);
				int Errors = 0;
				for (int I = Pos - Levels[Lev].size; I >= LeftBound; I--)
				{
					if (I < Pos - Levels[Lev].size &&
						Block.ensemble_classifier->classify(*Data[I]) != Data[I]->back().discr)
						Errors++;
					if (BestErrors[I] + Errors + Block.verror * Levels[Lev].size < LevErrors[Lev])
					{
						LevErrors[Lev] = BestErrors[I] + Errors + Block.verror * Levels[Lev].size;
						LevCuts[Lev] = I;
					}
				}
			}
			if (LevCuts[Lev] >= 0 && LevErrors[Lev] <= BestErrors[Pos] + 1e-3)
			{
				BestErrors[Pos] = LevErrors[Lev];
				BestCuts[Pos] = LevCuts[Lev];
			}
		}
	}
	LTask.progress("");
}

void vem_concept_cutter::find_chunks()
{
	vector<int> Cuts;
	for (int Pos = Data.size(); Pos >= 0; Pos = BestCuts[Pos])
		Cuts.push_back(Pos);
	for (int I = 0; I < (int)Cuts.size() / 2; I++)
		swap(Cuts[I], Cuts[Cuts.size() - 1 - I]);
	Chunks.resize(Cuts.size() - 1);
	for (int I = 0; I < (int)Chunks.size(); I++)
	{
		Chunks[I].begin = Cuts[I];
		Chunks[I].end = Cuts[I + 1];
		smart_ptr<validation_dataset> VDataset = BaseVDataset->clone();
		VDataset->build(make_subarray_to(Data, Chunks[I].begin, Chunks[I].end));
		Chunks[I].vdataset = VDataset;
		Chunks[I].vclassifier = Chunks[I].vdataset->train(&*BaseClassifier);
		Chunks[I].verror = Chunks[I].vclassifier->test_error(&*Chunks[I].vdataset);
	}
}

void vem_concept_cutter::make_block(block& Block, int Begin, int End)
{
	smart_ptr<validation_dataset> VDataset = BaseVDataset->clone();
	VDataset->build(make_subarray_to(Data, Begin, End));
	Block.vclassifier = VDataset->train(&*BaseClassifier);
	Block.ensemble_classifier = Block.vclassifier->get_ensemble_classifier();
	Block.verror = Block.vclassifier->test_error(&*VDataset);
}

bool vem_concept_cutter::good_block(int End, int Lev)
{
	if (!Prune || BestCuts[End] < 0 || Lev <= (int)Levels.size() / 2) return true;
	int Begin = End - Levels[Lev].size;
	int MaxSpan = -1;
	for (int Pos = End; Pos > Begin;)
	{
		int Next = BestCuts[Pos];
		if (Next < Begin) Next = Begin;
		if (Pos - Next > MaxSpan) MaxSpan = Pos - Next;
		Pos = Next;
	}
	return MaxSpan * 3 >= End - Begin;
}

int vem_concept_cutter::left_bound_of_block(int End, int Lev)
{
	int LeftBound = 0;
	for (int I = Lev + 1; I < (int)Levels.size(); I++)
		if (End >= Levels[I].size)
		{
			int Temp = (End - Levels[I].size) / Levels[I].offset * Levels[I].offset;
			if (Temp > LeftBound) LeftBound = Temp;
		}
	return LeftBound;
}

}

}
